# -*- coding: utf-8 -*-
# @Author: kaiquan
# @Date: 2025/7/14
# File: new_device
# -*- coding: utf-8 -*-
"""
@Time ： 2025/1/10 11:02
@Auth ： Muxiyy
@File ：device.py
"""
import datetime
import uiautomator2
import time
import json
import os
import re
from functools import cached_property
from typing import NamedTuple

work_dir = os.path.dirname(os.path.abspath(__file__))


class ShellResponse(NamedTuple):
    output: str
    exit_code: int


class Device():
    def __init__(self, sagit=None, device_ip="127.0.0.1", port="5555"):
        # super().__init__(udid)
        self.sagit = sagit
        self.online = True
        self.device_ip = device_ip
        self.port = port
        if device_ip != "127.0.0.1":
            self.connect = uiautomator2.connect(f"{device_ip}:{port}")
        else:
            self.connect = uiautomator2.connect(self.sagit)
        self.last_online_time = time.time()
        self.offline_callback = None

    def __call__(self, *args, **kwagrs):
        return self.connect.__call__(*args, **kwagrs)

    def sagit(self):
        return self.sagit()

    def reconnect(self):
        try:
            if self.device_ip != "127.0.0.1":
                self.connect = uiautomator2.connect(f"{self.device_ip}:{self.port}")
            else:
                self.connect = uiautomator2.connect(self.sagit)
        except:
            print(f'{self.connect.serial} Reconnect exception')

    def get_page(self):
        try:
            return self.connect.dump_hierarchy(compressed=False)
        except Exception as e:
            self.reconnect()
            print(f'{self.connect.serial} Get page exception:', str(e))
        return ''

    @cached_property
    def has_su(self):
        return self.connect.shell('which su').exit_code == 0

    def shell(self, cmd, timeout: int = 60, user=None):
        try:
            if self.has_su and user:
                cmd = f'su {user} sh -c "{cmd}"'
            output, code = self.connect.shell(cmd, timeout=timeout)
            # print(cmd, output, code)
            return ShellResponse(output, code)
        except Exception as e:
            print(e)
        return ShellResponse('', 1)

    __attrs = ('resourceId', 'text', 'description', 'className')
    __attrs_matches = ('textMatches', 'descriptionMatches')
    __attrs_contains = ('textContains', 'descriptionContains')

    def back(self):
        self.shell('input keyevent BACK')

    def home(self):
        self.shell('input keyevent HOME')

    def enter(self):
        self.shell('input keyevent ENTER')

    @property
    def display(self):
        if hasattr(self, '_display') and self._display:
            return self._display
        else:
            display_info = self.shell('wm size').output
            if display_info:
                m = re.search(r'(\d+)x(\d+)', display_info)
                if m:
                    self._display = (int(m.group(1)), int(m.group(2)))
                    return self._display

    def screen_on(self):
        result = self.shell('dumpsys power|grep state').output
        if not 'ON' in result:
            self.shell('input keyevent KEYCODE_POWER')

    def swipe_up(self, percent=0.5, duration=1):
        display = self.display
        self.shell(f'input swipe {display[0] / 2} {display[1] * percent} {display[0] / 2} 0 {int(1000 * duration)}')

    def set_text(self, tag, text, index=0):
        for attr in Device.__attrs:
            uiobjs = self.connect(**{attr: tag})
            try:
                if uiobjs.exists():
                    uiobjs[index].set_text(text)
                    return True
            except:
                break
        return False

    def swipe(self, x1, y1, x2, y2, duration=1):
        if x1 < 1:
            x1 *= self.display[0]
        if y1 < 1:
            y1 *= self.display[1]
        if x2 < 1:
            x2 *= self.display[0]
        if y2 < 1:
            y2 *= self.display[1]
        self.shell(f'input swipe {x1} {y1} {x2} {y2} {int(duration * 1000)}')

    def push(self, src, dst):
        try:
            self.connect.push(src, dst)
        except:
            return False
        return True

    def pull(self, src, dst):
        try:
            self.connect.pull(src, dst)
        except:
            return False
        return True

    # def pull_text(self, src):
    #     data_dir = os.path.join(work_dir, "device_file")
    #     os.makedirs(data_dir, exist_ok=True)
    #     file_name = f"{self.sagit}_{int(time.time())}.txt"
    #     a = self.pull(src, os.path.join(work_dir, f'device_file/{file_name}'))
    #     print(a)

    def click_xy(self, x, y):
        if x < 1:
            x *= self.display[0]
        if y < 1:
            y *= self.display[1]
        self.shell(f'input tap {x} {y}')

    def ip(self):
        return self.connect.wlan_ip

    def input_text(self, text):
        self.shell('input text "' + text + '"')

    def unlock(self):
        self.screen_on()
        self.swipe_up(0.8)
        self.shell("settings put system screen_off_timeout 2147483647")
        self.shell('input keyevent KEYCODE_BACK')
        self.shell('input keyevent KEYCODE_HOME')
        self.click('CANCEL')

    def click(self, tag, index=0, ignore_case=True):
        for attr in Device.__attrs:
            uiobjs = self.connect(**{attr: tag})
            try:
                if uiobjs.exists():
                    uiobjs[index].click()
                    return True
            except:
                break
        if ignore_case:
            for attr in Device.__attrs_matches:
                if '?' in tag:
                    tag = tag.replace('?', '\?')
                uiobjs = self.connect(**{attr: '(?i)' + tag})
                try:
                    if uiobjs.exists():
                        uiobjs[index].click()
                        return True
                except:
                    break
        return False

    def click_contains(self, tag, index=0):
        for attr in Device.__attrs_contains:
            uiobjs = self.connect(**{attr: tag})
            try:
                if uiobjs.exists():
                    uiobjs[index].click()
                    return True
            except:
                break
        return False

    def cat(self, path):
        data = self.shell("cat " + path).output
        if data and not "No such file or directory" in data:
            return data
        return None

    @property
    def model(self):
        if hasattr(self, '_model') and self._model:
            return self._model
        else:
            model = self.shell('getprop ro.product.model').output
            if model:
                self._model = model.strip()
                return self._model
            else:
                return ''

    @property
    def sdcard0(self):
        if hasattr(self, '_sd_path') and self._sd_path:
            return self._sd_path
        else:
            if self.model == 'PP5200' or self.model == 'Nexus 5':
                self._sd_path = '/storage/sdcard0/'
            else:
                self._sd_path = '/storage/emulated/0/'
            return self._sd_path

    @property
    def imei(self):
        if hasattr(self, '_imei') and self._imei:
            return self._imei
        elif self.cat(self.sdcard0 + 'imei.txt'):
            imei = self.cat(self.sdcard0 + 'imei.txt')
            if imei and imei.strip():
                self._imei = imei
                return imei
            else:
                os.remove(self.sdcard0 + 'imei.txt')
        else:
            result = self.shell('service call iphonesubinfo 1').output
            digit_parts = re.findall(r'\'([\S\s]+?)\'', result)
            imei = ''
            if digit_parts:
                for each in digit_parts:
                    imei += each.replace('.', '')
                imei = imei.strip()

            # 获取不到(如pptv没权限)，尝试用msp生成的deviceinfo.txt
            # Note(R3296):
            # - deviceId 取值是一下的任意一个
            #   - ESN [8]
            #   - MEID [14]
            #   - IMEI [15,17]
            if len(imei) < 8 or len(imei) > 17:
                imei = ''
                self.run_msp(True)
                self.shell('am broadcast -a com.msp.GET_DEVICE_INFO')
                time.sleep(1)
                deviceinfo = self.cat(f'{self.sdcard0}deviceinfo.txt')
                if deviceinfo:
                    try:
                        deviceinfo_json = json.loads(deviceinfo)
                        imei = deviceinfo_json['guid'].strip()
                    except:
                        pass

            self.shell(f'echo -n {imei}>{self.sdcard0}/imei.txt')
            self._imei = imei
            return imei

    @property
    def serial(self):
        if hasattr(self, '_serial') and self._serial:
            return self._serial
        else:
            serial = self.shell('getprop ro.serialno').output
            if serial:
                self._serial = serial.strip()
                return self._serial
            else:
                return ''
